package anatex.kea.genex;

import java.util.*;

import org.apache.log4j.Logger;
import org.jfree.base.log.LogConfiguration;
import org.jfree.util.LogContext;

import anatex.kea.*;
import anatex.kea.genex.extractor.*;
import anatex.kea.genex.genitor.*;

import anatex.domain.Document;
import anatex.domain.Keyword;
import anatex.domain.KeywordDocument;
import anatex.domain.TaskLog;
import anatex.domain.Task;
import anatex.domain.TrainingDocument;
import anatex.domain.KeywordExtractionModel;


public class Genitor {
	protected TreeSet<Object[]> population;
	protected int initailPopulationSize = 200;
	protected int corssovers = 1050;
	protected Double populationFitness;
	protected Extractor ex;
	protected Selector sel;
	protected Crossover cross;
	protected Document document;
	protected List<TrainingDocument> trainingDocuments;
	protected KeywordExtractionModel model;
	protected Logger logger;
	
	public Genitor() throws KeywordExtractorAlgoException {
		
		population = new TreeSet<Object[]>(Assessor.cmp);
		sel = new Selector(population);
		ex = new Extractor();
		cross = new Crossover();
		
		populationFitness = 0.0;
		
		logger = Logger.getLogger("genitor");
		logger.setLevel(org.apache.log4j.Level.INFO);
	}
	public Genitor(Document doc) throws KeywordExtractorAlgoException {
		super();
		
		document = doc;
		
		ex.setDocumentText(document);
	}
	
	protected Integer getRandomInteger(int min, int max) {
		
		return new Integer(min + (int)(Math.random() * ((max - min) + 1)));
	}
	
	protected Double getRandomDouble(Double min, Double max) {
		
		return new Double(min + (Math.random() * ((max - min) + 1)));
	}
	
	protected String createChromosome() {
		
		Parameters p = new Parameters();
		
		p.put(Parameters.NUM_PHRASES, getRandomInteger(ParametersDescriptor.NUM_PHRASES_MIN, ParametersDescriptor.NUM_PHRASES_MAX));
		p.put(Parameters.FACTOR_TWO_ONE, getRandomDouble(ParametersDescriptor.FACTOR_TWO_ONE_MIN, ParametersDescriptor.FACTOR_TWO_ONE_MAX));
		p.put(Parameters.FACTOR_THREE_ONE, getRandomDouble(ParametersDescriptor.FACTOR_THREE_ONE_MIN, ParametersDescriptor.FACTOR_THREE_ONE_MAX));
		p.put(Parameters.MIN_LENGTH_LOW_RANK, getRandomDouble(ParametersDescriptor.MIN_LENGTH_LOW_RANK_MIN, ParametersDescriptor.MIN_LENGTH_LOW_RANK_MAX));
		p.put(Parameters.MIN_RANK_LOW_LENGTH, getRandomInteger(ParametersDescriptor.MIN_RANK_LOW_LENGTH_MIN, ParametersDescriptor.MIN_RANK_LOW_LENGTH_MAX));
		p.put(Parameters.FIRST_LOW_THRESH, getRandomInteger(ParametersDescriptor.FIRST_LOW_THRESH_MIN, ParametersDescriptor.FIRST_LOW_THRESH_MAX));
		p.put(Parameters.FIRST_HIGH_THRESH, getRandomInteger(ParametersDescriptor.FIRST_HIGH_THRESH_MIN, ParametersDescriptor.FIRST_HIGH_THRESH_MAX));
		p.put(Parameters.FIRST_LOW_FACTOR, getRandomDouble(ParametersDescriptor.FIRST_LOW_FACTOR_MIN, ParametersDescriptor.FIRST_LOW_FACTOR_MAX));
		p.put(Parameters.FIRST_HIGH_FACTOR, getRandomDouble(ParametersDescriptor.FIRST_HIGH_FACTOR_MIN, ParametersDescriptor.FIRST_HIGH_FACTOR_MAX));
		p.put(Parameters.STEM_LENGTH, getRandomInteger(ParametersDescriptor.STEM_LENGTH_MIN, ParametersDescriptor.STEM_LENGTH_MAX));
		p.put(Parameters.SUPPRESS_PROPER, new Integer(new Random().nextBoolean() == false ? 0 : 1));
		
		return p.toBinaryString();
	}
	
	protected Boolean addToPopulation(String chromosome) {
		AbstractAlgo.logSystemStatus();
		
		Double chromosomeFitness 	= 0d;
		Parameters p 				= new Parameters();
		
		p.setParameters(chromosome);
		
		ex.setParameters(p);
		
		for (TrainingDocument td : trainingDocuments) {
			ex.setDocumentText(td.getDocument());
			
			ex.extract();
			
			//TODO May be it is not a good idea to allow existing items into population...
			chromosomeFitness += ex.getFitness();
		}
		
		chromosomeFitness = chromosomeFitness / trainingDocuments.size();
		populationFitness += chromosomeFitness;
		
		Object[] item = {chromosome, chromosomeFitness}; 
		population.add(item);
		
		return true;
	}
	
	protected void initPopulation() {
		
		for (int i = 0; i < initailPopulationSize; i ++) {
			if(! addToPopulation(createChromosome())) {
//				System.out.println("Cnadidate already in populate - try again");
			}
		}
	}
	
	public void doTheEvolution() {
		
		initPopulation();
		
		String[] parents;
		for (int i = 0; i < corssovers; i ++) {
			System.out.println("CROSSOVER #" + i);
			
			parents = sel.select();
			
//			System.out.println(
//					"First parent is " + parents[0] + "\n" +
//					"Second parent is " + parents[1] + "\n"
//			);
			
			cross.setAverageFitness(populationFitness / population.size());
			String chromosome = cross.crossover(parents[0], parents[1]);
			cross.mutate(parents, chromosome);
			
			System.out.print("Adding to population " + chromosome);
			addToPopulation(chromosome);
			
			//trim the population - remove the lowest ranking chromosome
//			System.out.println("First fitness is: " + population.first()[1]);
//			System.out.println("Last fitness is: " + population.last()[1]);
			
			populationFitness -= (Double)population.last()[1];
			population.pollLast();
//			population.remove(population.last());
			
			/* Tuk da logvam za tekushtia task za tekushtata iteracia srednia fitness
			 * (populationFitness / population.size
			 * entity task log primerno
			 * task, iteracia, sreden fitness, tekushto vreme,
			 * 
			 */
			//disable logger to test training set
//			TaskLog logger = new TaskLog();
//			logger.setIteration(i);
//			logger.setLogTime(new Date());
//			logger.setAverageFitness(populationFitness / population.size());
//			logger.setTask(Task.findTaskByDocument(document));
//			
//			logger.persist();
		}
		
		if(population.size()>0) {
//			System.out.println("Evaluating top candidate");
			
			String topChromosome = (String)population.first()[0];
			
			Parameters p = new Parameters();
			p.setParameters(topChromosome);
			
			ex.setParameters(p);
			
			for (TrainingDocument td : trainingDocuments) {
				extractAndSaveKeywordsForDocument(td.getDocument());
			}
			
			//save trained model to db
			model = new KeywordExtractionModel();
			model.setDocumentDomain(trainingDocuments.get(0).getDocument().getDocumentDomain());
			model.setModel(topChromosome);
			model.persist();
		}
		
	}
	
	public void setTrainingDocuments (List<TrainingDocument> trainingDocuments) {
		this.trainingDocuments = trainingDocuments;
	}
	
	/**
	 * Extracts and saves related key phrases for the specified document
	 * Parameters must be preset to extractor
	 * 
	 * @param doc
	 */
	public void extractAndSaveKeywordsForDocument (Document doc) {
		ex.setDocumentText(doc);
		ex.extract();
		
		logger.info(ex.getDocumentText().toString());
		
		TreeSet<Object[]> ts = ex.getExtractedKeyPhrases();
		
		for(Object[] keyPhrase : ts) {
			//check for existing keyword
			Keyword saveKeyword = null;
			try {
				saveKeyword = Keyword.findKeywordsByText((String)keyPhrase[0]).getSingleResult();
			} catch (Exception e) {
				saveKeyword = new Keyword();
				saveKeyword.setText((String)keyPhrase[0]);
				saveKeyword.setLocale(ex.getDocumentText().getCustomLocale());
				
				saveKeyword.persist();
			}
			
			//check for relation between keyword and docuemnt
			KeywordDocument ktd = null;
			try {
				ktd = KeywordDocument.findKeywordDocumentsByKeywordAndDocument(saveKeyword, ex.getDocumentText()).getSingleResult();
				ktd.setWeight(ktd.getWeight() + (Double)keyPhrase[1]);
			} catch (Exception e) {
				ktd = new KeywordDocument();
				
				ktd.setDocument(ex.getDocumentText());
				ktd.setKeyword(saveKeyword);
				ktd.setWeight((Double)keyPhrase[1]);
				
				ktd.persist();
			}
		}
	}
	
	public void setModel (KeywordExtractionModel model) {
		this.model = model;
		
		Parameters p = new Parameters();
		p.setParameters(this.model.getModel());
		ex.setParameters(p);
	}
	
}
